#!/usr/bin/env python3

from math import log
import sys
import os

mlist = 'mlist'
err_enum = 'errenum.h'
err_table = 'errtbl.h'
err_dir = '.'

''' {'ERR' : {'INVALID_MODULE_ID':'Invalid module id.'}} '''
modules_db = {}

enum = '''
enum {
%s
};

'''

enum_error = '''
enum {
%s
};
'''

err_desc = '''
err_proc g_err_proc_%s[ERR_%s_MAX_ERR] = {
%s
};
'''

err_desc_all = '''
err_proc dbg_g_err_proc_all[ERR_MAX_ERR] = {
%s
};
'''

err_procs = '''
err_proc *g_err_procs[%s] = {
%s
};
'''

err_mnm = '''
enum {
%s
};
'''

err_max_tbl = '''
uint32_t g_err_max_tbl[ERR_MAX_MODULE] = {
%s
};
'''

dbg_err_arr = '''
uint32_t dbg_g_err_arr[ERR_MAX_ERR] = {
%s
};
'''


def load_modules():
    with open(mlist, 'r') as f:
        mods = f.readlines()
        for i in range(len(mods)):
            mods[i] = mods[i].strip('\n')
            with open(mods[i], 'r') as mf:
                lines = mf.readlines()
                # print(lines)
                modules_db[mods[i]] = {}
                for l in lines:
                    d = l.split()
                    if d.__len__() >= 2:
                        modules_db[mods[i]][d[0]] = ' '.join(d[1:])


g_enm = ''
g_enm_error = ''
g_err_mnm = ''
g_err_procs = ''
g_desc = ''
g_desc_all = ''
g_idx_bit = 0
g_idx_msk = 0
g_mod_max_err = ''
g_err_max_tbl = ''
g_dbg_err_arr = ''
g_max_err = 0


def err_count():
    n = 0
    global g_enm
    global g_enm_error
    global g_desc
    global g_desc_all
    global g_err_procs
    global g_err_mnm
    global g_idx_bit
    global g_idx_msk
    global g_mod_max_err
    global g_err_max_tbl
    global g_dbg_err_arr
    global g_max_err
    g_enm = ''
    g_enm_error = ''
    for (k, v) in modules_db.items():
        n += v.__len__()
        e = ''
        for (ek, ev) in v.items():
            # print(f'{ek}, {ev}')
            e += ('E_' + k + '_' + ek + ',\n')
        if e != '':
            g_enm += enum % (e)

    # now we get total error code num
    nmax = int(log(n, 2)) + 1
    module_bit = nmax
    module_id = 0

    g_desc = ''
    g_err_mnm = ''
    mnm = ''
    procs = ''
    g_idx_bit = module_bit
    g_idx_msk = (1 << module_bit) - 1
    max_tbl_items = ''
    dbg_arr_items = ''
    desc_items_all = ''
    for (k, v) in modules_db.items():
        e = ''
        idx = 1
        err_msk = module_id << module_bit
        desc_items = ''
        for (ek, ev) in v.items():
            # print(err_msk | idx)
            e += ('ERR_' + k + '_' + ek + ' = %s,\n' % (err_msk | idx))
            idx = idx + 1
            desc_items += ('{%s, %s},\n' % ('ERR_' + k + '_' + ek, ev))
            dbg_arr_items += ('ERR_' + k + '_' + ek + ',\n')
        if desc_items != '':
            desc_items_all += desc_items
            g_mod_max_err += '#define ERR_%s_MAX_ERR %s\n' % (k, idx - 1)
            g_max_err += (idx - 1)
            max_tbl_items += "ERR_%s_MAX_ERR,\n" % k
            g_desc += err_desc % (str(k).lower(), k, desc_items)
            mnm += 'ERR_MODULE_%s,\n' % k
            g_enm_error += enum_error % e
            procs += ('g_err_proc_%s,\n' % (str(k).lower()))
            module_id += 1
    if mnm != '':
        mnm += 'ERR_MAX_MODULE\n'
        g_err_mnm = err_mnm % mnm
        g_err_procs = err_procs % ('ERR_MAX_MODULE', procs)
        g_err_max_tbl = err_max_tbl % max_tbl_items
        g_dbg_err_arr = dbg_err_arr % dbg_arr_items
        g_desc_all = err_desc_all % desc_items_all

    # print(g_enm)
    # print(g_enm_error)
    # print(g_desc)


def write_to_file(h_file, s_file):
    with open(h_file, 'w') as h:
        # h.writelines(g_enm)
        h.writelines(
            '''#ifndef ENET_ERR_ER_H
#define ENET_ERR_ER_H'''
        )
        h.writelines(g_err_mnm)
        h.writelines(g_enm_error)
        h.writelines(g_mod_max_err)
        h.writelines('#define ERR_IDX_BIT %s\n' % g_idx_bit)
        h.writelines('#define ERR_IDX_MASK %s\n' % g_idx_msk)
        h.writelines('#define ERR_MAX_ERR %s\n' % g_max_err)
        h.writelines('#define ERR_MOD_IDX(x) ((x) >> ERR_IDX_BIT)\n')
        h.writelines('#define ERR_ERR_IDX(x) (((x) & ERR_IDX_MASK) - 1)\n')
        h.writelines('#endif')
    with open(s_file, 'w') as c:
        c.writelines(
            '''#ifndef ENET_ERR_S_H
#define ENET_ERR_S_H
#include "err.h"'''
        )
        c.writelines(g_desc)
        c.writelines(g_err_procs)
        c.writelines(g_err_max_tbl)
        c.writelines('\n\n#if defined(DEBUG_ERR)')
        c.writelines(g_dbg_err_arr)
        c.writelines(g_desc_all)
        c.writelines('#endif\n\n')
        c.writelines('#endif')


if sys.argv.__len__() < 4:
    print('%s error: Usage: errgen.py <cwd> <h_file> <s_file>' % sys.argv[0])
    exit(1)

os.chdir(sys.argv[1])
load_modules()
err_count()
if g_max_err != 0:
    write_to_file(sys.argv[2], sys.argv[3])
